/*
 * GPUC
 * 
 * Copyright © 2008 Attila T. Áfra <attila.afra@gmail.com>
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program. If
 * not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include <vector>
#include <string>
#include "Common.hpp"
#include "Uncopyable.hpp"
#include "Exception.hpp"
#include "Stream.hpp"

/*------------------------------------------------------------------------------------------------*/

namespace GPUC
{
    class Kernel : Uncopyable
    {
    public:
        static int getMaxOutputStreamCount();
        
        explicit Kernel(const char* sourceCode);
        explicit Kernel(const std::string& sourceCode);
        ~Kernel();

        GPUC_Kernel getObject() const;

        int getParameterCount() const;
        int getParameterIndex(const char* name) const;
        int getParameterIndex(const std::string& name) const;
        std::string getParamaterName(int parameterIndex) const;
        Type getParameterType(int parameterIndex) const;
        int getParamaterLength(int parameterIndex) const;
        
        void setOutputStream(Stream& stream);
        void setOutputStream(std::vector<Stream>& streams);
        void setOutputStream(Stream* streams, int streamCount);
        void setOutputStream(std::vector<Stream*>& streams);
        void setOutputStream(Stream** streams, int streamCount);

        void setInputStream(int parameterIndex, Stream& value);
        void setInputStream(int parameterIndex, std::vector<Stream>& values);
        void setInputStream(int parameterIndex, Stream* values);
        void setInputStream(int parameterIndex, std::vector<Stream*>& values);
        void setInputStream(int parameterIndex, Stream** values);
        void setInputFloat(int parameterIndex, const float* values);
        void setInputFloat2(int parameterIndex, const float* values);
        void setInputFloat3(int parameterIndex, const float* values);
        void setInputFloat4(int parameterIndex, const float* values);
        void setInputInt(int parameterIndex, const int* values);
        void setInputInt2(int parameterIndex, const int* values);
        void setInputInt3(int parameterIndex, const int* values);
        void setInputInt4(int parameterIndex, const int* values);
        void setInputBool(int parameterIndex, const int* values);
        void setInputBool2(int parameterIndex, const int* values);
        void setInputBool3(int parameterIndex, const int* values);
        void setInputBool4(int parameterIndex, const int* values);
        void setInputFloat2x2(int parameterIndex, const float* values);
        void setInputFloat3x3(int parameterIndex, const float* values);
        void setInputFloat4x4(int parameterIndex, const float* values);

        void invoke();
        void invoke(const Rectangle& range);

    private:
        GPUC_Kernel kernel;
    };
}

/*------------------------------------------------------------------------------------------------*/

int
GPUC::Kernel::getMaxOutputStreamCount()
{
    const int result = GPUC_Kernel_getMaxOutputStreamCount();
    Internal::checkError();
    return result;
}

/*------------------------------------------------------------------------------------------------*/

GPUC::Kernel::Kernel(const char* sourceCode)
{
    kernel = GPUC_Kernel_create(sourceCode);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

GPUC::Kernel::Kernel(const std::string& sourceCode)
{
    kernel = GPUC_Kernel_create(sourceCode.c_str());
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

GPUC::Kernel::~Kernel()
{
    GPUC_Kernel_destroy(kernel);
}

/*------------------------------------------------------------------------------------------------*/

GPUC_Kernel GPUC::Kernel::getObject() const
{
    return kernel;
}

/*------------------------------------------------------------------------------------------------*/

int GPUC::Kernel::getParameterCount() const
{
    return GPUC_Kernel_getParameterCount(kernel);
}

/*------------------------------------------------------------------------------------------------*/

int GPUC::Kernel::getParameterIndex(const char* name) const
{
    const int result = GPUC_Kernel_getParameterIndex(kernel, name);
    Internal::checkError();
    return result;
}

/*------------------------------------------------------------------------------------------------*/

int GPUC::Kernel::getParameterIndex(const std::string& name) const
{
    const int result = GPUC_Kernel_getParameterIndex(kernel, name.c_str());
    Internal::checkError();
    return result;
}

/*------------------------------------------------------------------------------------------------*/

std::string GPUC::Kernel::getParamaterName(int parameterIndex) const
{
    const char* result = GPUC_Kernel_getParameterName(kernel, parameterIndex);
    Internal::checkError();
    return result;
}

/*------------------------------------------------------------------------------------------------*/

GPUC::Type GPUC::Kernel::getParameterType(int parameterIndex) const
{
    GPUC_Type result = GPUC_Kernel_getParameterType(kernel, parameterIndex);
    Internal::checkError();
    return (Type)result;
}

/*------------------------------------------------------------------------------------------------*/

int GPUC::Kernel::getParamaterLength(int parameterIndex) const
{
    int result = GPUC_Kernel_getParameterLength(kernel, parameterIndex);
    Internal::checkError();
    return result;
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setOutputStream(GPUC::Stream& stream)
{
    GPUC_Stream rawStream = stream.getObject();
    GPUC_Kernel_setOutputStream(kernel, &rawStream, 1);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setOutputStream(std::vector<GPUC::Stream>& streams)
{
    std::vector<GPUC_Stream> rawStreams(streams.size());

    for (size_t i = 0; i < streams.size(); ++i)
        rawStreams[i] = streams[i].getObject();

    GPUC_Kernel_setOutputStream(kernel, &rawStreams.front(), rawStreams.size());
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setOutputStream(GPUC::Stream* streams, int streamCount)
{
    std::vector<GPUC_Stream> rawStreams(streamCount);

    for (int i = 0; i < streamCount; ++i)
        rawStreams[i] = streams[i].getObject();

    GPUC_Kernel_setOutputStream(kernel, &rawStreams.front(), streamCount);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setOutputStream(std::vector<GPUC::Stream*>& streams)
{
    std::vector<GPUC_Stream> rawStreams(streams.size());

    for (size_t i = 0; i < streams.size(); ++i)
        rawStreams[i] = streams[i]->getObject();

    GPUC_Kernel_setOutputStream(kernel, &rawStreams.front(), rawStreams.size());
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setOutputStream(GPUC::Stream** streams, int streamCount)
{
    std::vector<GPUC_Stream> rawStreams(streamCount);

    for (int i = 0; i < streamCount; ++i)
        rawStreams[i] = streams[i]->getObject();

    GPUC_Kernel_setOutputStream(kernel, &rawStreams.front(), streamCount);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputStream(int parameterIndex, GPUC::Stream& value)
{
    if (GPUC_Kernel_getParameterLength(kernel, parameterIndex) != 1)
        throw InvalidArgumentException();

    GPUC_Stream rawStream = value.getObject();
    GPUC_Kernel_setInputStream(kernel, parameterIndex, &rawStream);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputStream(int parameterIndex, std::vector<Stream>& values)
{
    if (static_cast<size_t>(GPUC_Kernel_getParameterLength(kernel, parameterIndex)) != values.size())
        throw InvalidArgumentException();

    std::vector<GPUC_Stream> rawValues(values.size());

    for (size_t i = 0; i < rawValues.size(); ++i)
        rawValues[i] = values[i].getObject();

    GPUC_Kernel_setInputStream(kernel, parameterIndex, &rawValues.front());
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputStream(int parameterIndex, Stream* values)
{
    std::vector<GPUC_Stream> rawValues(GPUC_Kernel_getParameterLength(kernel, parameterIndex));

    for (size_t i = 0; i < rawValues.size(); ++i)
        rawValues[i] = values[i].getObject();

    GPUC_Kernel_setInputStream(kernel, parameterIndex, &rawValues.front());
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputStream(int parameterIndex, std::vector<Stream*>& values)
{
    if (static_cast<size_t>(GPUC_Kernel_getParameterLength(kernel, parameterIndex)) != values.size())
        throw InvalidArgumentException();

    std::vector<GPUC_Stream> rawValues(values.size());

    for (size_t i = 0; i < rawValues.size(); ++i)
        rawValues[i] = values[i]->getObject();

    GPUC_Kernel_setInputStream(kernel, parameterIndex, &rawValues.front());
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputStream(int parameterIndex, Stream** values)
{
    std::vector<GPUC_Stream> rawValues(GPUC_Kernel_getParameterLength(kernel, parameterIndex));

    for (size_t i = 0; i < rawValues.size(); ++i)
        rawValues[i] = values[i]->getObject();

    GPUC_Kernel_setInputStream(kernel, parameterIndex, &rawValues.front());
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputFloat(int parameterIndex, const float* values)
{
    GPUC_Kernel_setInputFloat(kernel, parameterIndex, values);
    Internal::checkError();
}


/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputFloat2(int parameterIndex, const float* values)
{
    GPUC_Kernel_setInputFloat2(kernel, parameterIndex, values);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputFloat3(int parameterIndex, const float* values)
{
    GPUC_Kernel_setInputFloat3(kernel, parameterIndex, values);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputFloat4(int parameterIndex, const float* values)
{
    GPUC_Kernel_setInputFloat4(kernel, parameterIndex, values);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputInt(int parameterIndex, const int* values)
{
    GPUC_Kernel_setInputInt(kernel, parameterIndex, values);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputInt2(int parameterIndex, const int* values)
{
    GPUC_Kernel_setInputInt2(kernel, parameterIndex, values);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputInt3(int parameterIndex, const int* values)
{
    GPUC_Kernel_setInputInt3(kernel, parameterIndex, values);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputInt4(int parameterIndex, const int* values)
{
    GPUC_Kernel_setInputInt4(kernel, parameterIndex, values);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputBool(int parameterIndex, const int* values)
{
    GPUC_Kernel_setInputBool(kernel, parameterIndex, values);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputBool2(int parameterIndex, const int* values)
{
    GPUC_Kernel_setInputBool2(kernel, parameterIndex, values);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputBool3(int parameterIndex, const int* values)
{
    GPUC_Kernel_setInputBool3(kernel, parameterIndex, values);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputBool4(int parameterIndex, const int* values)
{
    GPUC_Kernel_setInputBool4(kernel, parameterIndex, values);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputFloat2x2(int parameterIndex, const float* values)
{
    GPUC_Kernel_setInputFloat2x2(kernel, parameterIndex, values);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputFloat3x3(int parameterIndex, const float* values)
{
    GPUC_Kernel_setInputFloat3x3(kernel, parameterIndex, values);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::setInputFloat4x4(int parameterIndex, const float* values)
{
    GPUC_Kernel_setInputFloat4x4(kernel, parameterIndex, values);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::invoke()
{
    GPUC_Kernel_invoke(kernel);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/

void GPUC::Kernel::invoke(const GPUC::Rectangle& range)
{
    GPUC_Kernel_partialInvoke(kernel, &range);
    Internal::checkError();
}

/*------------------------------------------------------------------------------------------------*/
